package com.chenwy.example.filter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CrossOriginFilter implements Filter {

    private Logger logger = LoggerFactory.getLogger(getClass());

//    private Logger loggerDatastat = LoggerFactory.getLogger("datastat-caltime");

//    private Logger loggerParamError = LoggerFactory.getLogger("paramerror-datafile");
    /**
     * 该字段是必须的。它的值要么是请求时Origin字段的值，要么是一个*，表示接受任意域名的请求。
     */
    private static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin";

    /**
     * 该字段可选，用来指定本次预检请求的有效期，单位为秒。上面结果中，有效期是20天（1728000秒），即允许缓存该条回应1728000秒（即20天），在此期间，不用发出另一条预检请求
     */
    private static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age";

    /**
     * 该字段必需，它的值是逗号分隔的一个字符串，表明服务器支持的所有跨域请求的方法。注意，返回的是所有支持的方法，而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
     */
    private static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods";

    /**
     * 如果浏览器请求包括Access-Control-Request-Headers字段，则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串，表明服务器支持的所有头信息字段，不限于浏览器在"预检"中请求的字段。
     */
    private static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers";

    /**
     * 该字段可选。它的值是一个布尔值，表示是否允许发送Cookie。默认情况下，Cookie不包括在CORS请求之中。设为true，即表示服务器明确许可，Cookie可以包含在请求中，一起发给服务器。这个值也只能设为true，如果服务器不要浏览器发送Cookie，删除该字段即可。
     */
    private static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials";

    /**
     * 该字段可选。CORS请求时，
     * XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段：
     * Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
     * 如果想拿到其他字段，就必须在Access-Control-Expose-Headers里面指定。
     * 上面的例子指定，getResponseHeader('FooBar')可以返回FooBar字段的值。
     */
    @SuppressWarnings("unused")
    private static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = "Access-Control-Expose-Headers";

    /**
     * 用来说明，本次请求来自哪个源（协议 + 域名 + 端口）
     */
    private static final String ORIGIN_HEADER = "Origin";

    /**
     * 该字段是必须的，用来列出浏览器的CORS请求会用到哪些HTTP方法，上例是PUT。
     */
    private static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method";

    /**
     * 该字段是一个逗号分隔的字符串，指定浏览器CORS请求会额外发送的头信息字段，上例是X-Custom-Header。
     */
    private static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers";

    /**
     * 允许跨域的参数
     */
    private static final String ALLOWED_ORIGINS = "allowedOrigins";

    /**
     * 允许的跨域请求方法
     */
    private static final String ALLOWED_METHODS = "allowedMethods";

    /**
     * 允许的跨域请求头
     */
    private static final String ALLOWED_HEADERS = "allowedHeaders";

    /**
     *
     */
    private static final String PREFLIGHT_MAX_AGE = "preflightMaxAge";

    /**
     * 表示是否允许发送Cookie
     */
    private static final String ALLOWED_CREDENTIALS = "allowedCredentials";

    /**
     * 允许任意的域
     */
    private static final String ANY_ORIGIN = "*";


    /**简单的http方法 */
    private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD","PUT","DELETE");

    /** The any origin allowed. */
    private boolean anyOriginAllowed = false;

    /** The allowed origins. */
    private List<String> allowedOrigins = new ArrayList<String>();

    /** The allowed methods. */
    private List<String> allowedMethods = new ArrayList<String>();

    /** The allowed headers. */
    private List<String> allowedHeaders = new ArrayList<String>();

    /** The preflight max age. */
    private int preflightMaxAge = 0;

    /** The allow credentials. */
    private boolean allowedCredentials = true;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        String allowedOrigins = filterConfig.getInitParameter(ALLOWED_ORIGINS);

        if(allowedOrigins == null){
            allowedOrigins = "*";
        }

        String [] allowedOriginsArray = allowedOrigins.split(",");

        for(String allowedOrigin : allowedOriginsArray){
            allowedOrigin = allowedOrigin.trim();
            if(allowedOrigin.length() > 0){
                if(ANY_ORIGIN.equals(allowedOrigin)){
                    anyOriginAllowed = true;
                    this.allowedOrigins.clear();
                    break;
                }else{
                    this.allowedOrigins.add(allowedOrigin);
                }
            }
        }

        String allowedMethods = filterConfig.getInitParameter(ALLOWED_METHODS);
        if(allowedMethods == null){
            allowedMethods = "GET,POST,PUT,DELETE";
        }
        this.allowedMethods.addAll(Arrays.asList(allowedMethods.split(",")));

        String allowedHeaders = filterConfig.getInitParameter(ALLOWED_HEADERS);

        if(allowedHeaders == null){
            allowedHeaders = "X-Requested-With,Content-Type,Accept,Origin";
        }
        this.allowedHeaders.addAll(Arrays.asList(allowedHeaders.split(",")));

        String preflightMaxAge = filterConfig.getInitParameter(PREFLIGHT_MAX_AGE);

        if(preflightMaxAge == null){
            //30分钟
            preflightMaxAge = "1800";
        }

        try{
            this.preflightMaxAge = Integer.parseInt(preflightMaxAge);
        }catch(Exception e){

        }
        String allowedCredentials = filterConfig.getInitParameter(ALLOWED_CREDENTIALS);
        if(allowedCredentials == null){
            allowedCredentials = "true";
        }
        try{
            this.allowedCredentials = Boolean.parseBoolean(allowedCredentials);
        }catch(Exception e){

        }
        System.out.println("---========------==============-----==========---");
        System.out.println(this.getClass().getName());
        logger.debug("Cross-origin filter configuration: " + ALLOWED_ORIGINS + " = " + allowedOrigins
                + ", " + ALLOWED_METHODS + " = " + allowedMethods + ", " + ALLOWED_HEADERS + " = "
                + allowedHeaders + ", " + PREFLIGHT_MAX_AGE + " = " + preflightMaxAge + ", "
                + ALLOWED_CREDENTIALS + " = " + allowedCredentials);

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        System.out.println("CrossOriginFilter doFilter");
        //处理拦截
        handle((HttpServletRequest)request, (HttpServletResponse)response, chain);
    }

    private void handle(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws IOException, ServletException{
//        if(request.getQueryString() != null && request.getQueryString().indexOf("undefined") != -1) {
//            StringBuilder sb = new StringBuilder();
//            sb.append("请求参数包含undefined值，url：" + request.getRequestURI() + "，query：" + request.getQueryString());
//            String jxtRoute = request.getHeader("jxt_route");
//            if(jxtRoute != null) {
//                jxtRoute = URLDecoder.decode(jxtRoute, "utf-8");
//                JSONArray routeArr = JSONArray.parseArray(jxtRoute);
//                for(int i = 0; i < routeArr.size(); i++) {
//                    sb.append("\r\n");
//                    JSONObject routePage = routeArr.getJSONObject(i);
//                    sb.append("jxtRoute：").append(routePage.getString("route")).append("，");
//                    sb.append("jxtOptions：").append(routePage.getString("options"));
//                }
//            }
//            loggerParamError.info(sb.toString());
//        }
//        if(request.getQueryString() != null && request.getQueryString().indexOf("NaN") != -1) {
//            StringBuilder sb = new StringBuilder();
//            sb.append("请求参数包含NaN值，url：" + request.getRequestURI() + "，query：" + request.getQueryString());
//            String jxtRoute = request.getHeader("jxt_route");
//            if(jxtRoute != null) {
//                jxtRoute = URLDecoder.decode(jxtRoute, "utf-8");
//                JSONArray routeArr = JSONArray.parseArray(jxtRoute);
//                for(int i = 0; i < routeArr.size(); i++) {
//                    sb.append("\r\n");
//                    JSONObject routePage = routeArr.getJSONObject(i);
//                    sb.append("jxtRoute：").append(routePage.getString("route")).append("，");
//                    sb.append("jxtOptions：").append(routePage.getString("options"));
//                }
//            }
//            loggerParamError.info(sb.toString());
//        }
        String origin = request.getHeader(ORIGIN_HEADER);
        /**
         * 带有origin头，且请求有效
         */
        if(origin != null && isEnabled(request)){
            /**
             * 判断该站点是否受允许
             */
            if(isOriginMatches(origin)){
                /**
                 * 是否为简单请求
                 */
                if(isSimpleRequest(request)){

                    logger.debug("Cross-origin request to {} is a simple cross-origin request",request.getRequestURI());
                    handleSimpleResponse(request,response,origin);
                }else{
                    logger.debug("Cross-origin request to {} is a preflight cross-origin request",request.getRequestURI());

                    handlePreflightResponse(request,response,origin);
                }

            }else{
                logger.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin
                        + " does not match allowed origins " + allowedOrigins);
            }
        }

//		logger.info("CrossOriginFilter start");
        long startTime = System.currentTimeMillis();
        request.setAttribute("processStartTime", startTime);

        chain.doFilter(request, response);
//		long endTime = System.currentTimeMillis();

//		logger.info("CrossOriginFilter");
//		addRequestLog(request, response, startTime, endTime);
    }



    /**
     * 请求是否有效
     * @param request
     * @return
     */
    private boolean isEnabled(HttpServletRequest request){
        /**
         * WebSocket
         */
        if("Upgrade".equalsIgnoreCase(request.getHeader("Connection"))&&"WebSocket".equalsIgnoreCase(request.getHeader("Upgrade"))){

            return false;
        }

        return true;
    }

    /**
     * 判断该站点是否受允许
     * @param origin
     * @return
     */
    private boolean isOriginMatches(String origin){
        if(anyOriginAllowed)
            return true;
        for(String allowedOrigin : allowedOrigins){
            if(allowedOrigin.equals(origin)){
                return true;
            }
        }
        return false;
    }

    /**
     * 是否为简单请求
     * @param request
     * @return
     */
    private boolean isSimpleRequest(HttpServletRequest request){
        if(SIMPLE_HTTP_METHODS.contains(request.getMethod())){
            return request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER) == null;
        }
        return false;
    }

    /**
     * 处理简单跨域的响应
     * @param request
     * @param response
     * @param origin
     */
    private void handleSimpleResponse(HttpServletRequest request,HttpServletResponse response,String origin){
        response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
        if(allowedCredentials){
            response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
        }
    }

    /**
     * 处理preflight跨域响应
     * @param request
     * @param response
     * @param origin
     */
    private void handlePreflightResponse(HttpServletRequest request,HttpServletResponse response,String origin){

        boolean methodAllowed = isMethodAllowed(request);

        if(!methodAllowed){
            return;
        }

        boolean headersAllowed = areHeadersAllowed(request);

        if(!headersAllowed){
            return;
        }

        response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);

        if(allowedCredentials){
            response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
        }

        if(preflightMaxAge > 0){
            response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge));
        }

        response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, StringUtils.join(allowedMethods.toArray(),","));

        response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, StringUtils.join(allowedHeaders.toArray(),","));
    }

    /**
     * 判断请求方法是否被允许
     * @param request
     * @return
     */
    private boolean isMethodAllowed(HttpServletRequest request){

        String accessControlRequestMethod = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER);
        logger.debug("{} is {}" ,ACCESS_CONTROL_REQUEST_METHOD_HEADER , accessControlRequestMethod);
        boolean result = false;
        if(accessControlRequestMethod != null){
            result = allowedMethods.contains(accessControlRequestMethod);
        }
        logger.debug("Method {} is" + (result ? "" : " not") + " among allowed methods {}" , accessControlRequestMethod,allowedMethods);

        return result;
    }

    /**
     * 判断是否请求头被允许
     * @param request
     * @return
     */
    private boolean areHeadersAllowed(HttpServletRequest request){
        String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER);
        logger.debug("{} is {}" + ACCESS_CONTROL_REQUEST_HEADERS_HEADER + accessControlRequestHeaders);
        boolean result = true;
        if (accessControlRequestHeaders != null) {
            String[] headers = accessControlRequestHeaders.split(",");
            for (String header : headers) {
                boolean headerAllowed = false;
                for (String allowedHeader : allowedHeaders) {
                    if (header.trim().equalsIgnoreCase(allowedHeader.trim())) {
                        headerAllowed = true;
                        break;
                    }
                }
                if (!headerAllowed) {
                    result = false;
                    break;
                }
            }
        }
        logger.debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}", accessControlRequestHeaders , allowedHeaders);
        return result;
    }

    private void addRequestLog(HttpServletRequest request,HttpServletResponse response, long startTime, long endTime) {
        try{
            String uri = request.getRequestURI();
            long processTime = endTime - startTime;
            final String referer = request.getHeader("referer");
            //https://servicewechat.com/wx6abc8e6c1ff6670a/0/page-frame.html
            String appId = "";
            if(StringUtils.isNotEmpty(referer) && referer.indexOf("servicewechat")>0) {
                String[] strArr = referer.split("/");
                appId = strArr[3];
            }
//            loggerDatastat.info(String.format("%s\t%s\t%s\t%s\t%s\t", appId, uri, startTime, endTime, processTime));
        } catch (Exception ex) {
            logger.error("记录请求日志异常", ex);
        }

    }

    @Override
    public void destroy() {
        anyOriginAllowed = false;
        allowedOrigins.clear();
        allowedMethods.clear();
        allowedHeaders.clear();
        preflightMaxAge = 0;
        allowedCredentials = false;
    }
}
